package org.deltaset.meta.faces;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.ManagedBean;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.inject.Inject;
import org.primefaces.model.LazyDataModel;
import org.deltaset.faces.FacesUtils;
import org.deltaset.faces.PageHelper;
import org.deltaset.faces.component.SelectOption;
import org.deltaset.meta.Catalog;
import org.deltaset.meta.CollectionHandler;
import org.deltaset.meta.EntityResolver;
import org.deltaset.meta.ExecutorResult;
import org.deltaset.meta.Finder;
import org.deltaset.meta.LabelResolver;
import org.deltaset.meta.MetaAction;
import org.deltaset.meta.MetaActionExecutor;
import org.deltaset.meta.MetaActionHandler;
import org.deltaset.meta.MetaEntry;
import org.deltaset.meta.MetaField;
import org.deltaset.meta.MetaHandlerFactory;
import org.deltaset.meta.OptionBuilder;
import org.primefaces.event.SelectEvent;
import org.springframework.context.annotation.Scope;
import org.springframework.web.context.WebApplicationContext;

/**
 *
 * @author dmakariev
 */
@ManagedBean("metaBean")
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class MetaCrudBean implements Serializable {

    private static final Logger logger = Logger.getLogger(MetaCrudBean.class.getName());
    //////////
    private final OptionBuilder<SelectOption> optionBuilder;
    private final Converter genericEntityConverter;
    private final Catalog catalog;
    private final EntityResolver entityResolver;
    private final CollectionHandler collectionHandler;
    private final MetaActionHandler metaActionHandler;
    private final LabelResolver labelResolver;
    private final MetaHandlerFactory metaHandlerFactory;
    private final Map<String, String> viewNameTemplateMap;
    private final MetaCrudSession metaCrudSession;
    ////////
    private PageHelper<Object> all;
    private String sourceIndex;
    private String targetIndex;

    @Inject
    public MetaCrudBean(final LazyInit lazyInit, MetaCrudSession metaCrudSession) {
        this.metaCrudSession = metaCrudSession;
        // this.lazyInit = lazyInit;
        this.optionBuilder = lazyInit.getOptionBuilder();
        this.genericEntityConverter = lazyInit.getGenericEntityConverter();
        this.catalog = lazyInit.getCatalog();
        this.entityResolver = lazyInit.getEntityResolver();
        this.collectionHandler = lazyInit.getCollectionHandler();
        this.metaActionHandler = lazyInit.getMetaActionHandler();
        this.labelResolver = lazyInit.getLabelResolver();

        this.metaHandlerFactory = lazyInit.getMetaHandlerFactory();
        this.viewNameTemplateMap = lazyInit.getViewNameTemplateMap();

    }

    public String getCurrentViewTemplate() {
        final String viewName = getAttributeContext().getViewName();
        if (null == viewName) {
            return null;
        }
        final String currentViewTemplate = viewNameTemplateMap.get(viewName);
        return currentViewTemplate;
    }

//    public void showDetail() {
//        this.setCurrentViewName("detailTemplate");
//    }
//
//    public void showList() {
//        this.setCurrentViewName("listTemplate");
//    }
//    public void forceChangeTo(Object item) {
//        if (null == item) {
//            return;
//        }
//        final String className = item.getClass().getName();
//        final String viewName = "formTemplate";
//        final String recordId = metaActionHandler.getModelId(item);
//        final AttributeContext newAttributeContext = new AttributeContext(className, viewName, recordId);
//
//        getHistoryStack().push(newAttributeContext);
//
//        this.isModelLoaded = false;
//        this.metaEntryState = null;
//        this.all = null;
//    }
    public void forceChangeTo(Object item) {
        if (null == item) {
            return;
        }
        final String viewName = "formTemplate";
        resetListHelper();
        metaCrudSession.setModel(item, viewName);
    }

    public boolean getDisabledPreviousView() {
        return metaCrudSession.hasPrevious();
    }

    public Object getModel() {
        return metaCrudSession.getModel();
    }

    private void setModel(final Object model) {
        metaCrudSession.setModel(model, null);
    }

    public void cancelExecutor() {
        getMetaEntryState().setExecutor(null);
    }

    public void loadExecutor(final MetaAction metaAction, final Object model) {
        /*
         * this is a 'special' case used by pagedTable for iterating <f:param
         * name="deltaset.meta.itemId" value="#{cc.attrs.itemId}"/> <f:param
         * name="deltaset.meta.itemClassName"
         * value="#{cc.attrs.itemClassName}"/>
         */
        final String itemId = FacesUtils.getParameterString("deltaset.meta.itemId");
        final String itemClassName = FacesUtils.getParameterString("deltaset.meta.itemClassName");
        if (null != itemId && null != itemClassName) {
            final MetaEntry itemMetaEntry = metaHandlerFactory.createMetaEntry(itemClassName);
            final Object item = metaActionHandler.loadModel(itemMetaEntry, itemId);
            loadExecutorInternal(metaAction, item);
        } else {
            loadExecutorInternal(metaAction, model);
        }
    }

    private void loadExecutorInternal(final MetaAction metaAction, final Object model) {
        resetListHelper();
        final MetaActionExecutor executor = metaHandlerFactory.createMetaActionExecutor(metaAction);
        if (true == metaAction.getHasParameter()) {
            this.setModel(model);
            getMetaEntryState().setExecutor(executor);
        } else {
            this.executeAction(executor, model);
        }
    }

    public void executeAction() {
        resetListHelper();
        final MetaActionExecutor executor = this.getExecutor();
        final Object model = this.getModel();
        executeAction(executor, model);
    }

    public MetaActionExecutor getExecutor() {
        return this.getMetaEntryState().getExecutor();
    }

    public ExecutorResult getExecutorResult() {
        return this.getMetaEntryState().getExecutorResult();
    }

    public boolean getShowFinderForm() {
        final MetaActionExecutor finder = getFinder();
        if (null == finder) {
            return false;
        }
        return finder.getMetaAction().getHasParameter();
    }

    public MetaActionExecutor getFinder() {
        return this.getMetaEntryState().getFinder();
    }

    public void cancelFinder() {
        this.getMetaEntryState().setFinder(null);
    }

    public void executeFinder() {
        //TODO: review this method..    
        resetListHelper();
        logger.log(Level.INFO, "executeFinder    ########");
    }

    public void loadFinder(final MetaAction metaAction) {
        //resetListHelper();
        final MetaActionExecutor executor = metaHandlerFactory.createMetaActionExecutor(metaAction);
        this.getMetaEntryState().setFinder(executor);

    }

    private Finder allFinder() {
        final MetaActionExecutor finderExecutor = getFinder();
        final MetaAction defaultFinder = getMetaEntryState().getMetaEntry().getDefaultFinder();
        final Finder finder = metaActionHandler.newPageHelper(finderExecutor, defaultFinder);
        return finder;
    }

    private void executeAction(final MetaActionExecutor executor, final Object model) {
        // this.all = null;
        final ExecutorResult executorResult = metaActionHandler.executeAction(executor, model);
        getMetaEntryState().setExecutor(null);
        getMetaEntryState().setExecutorResult(executorResult);

        final Object result = executorResult.getValue();
        final MetaAction metaAction = executorResult.getExecutedAction();

        final String methodName = metaAction.getMethodName();
        final String message = "executed '" + methodName + "'";
        addInfoMessage(message);

        if (true == metaAction.getIsReturningEntity()) {
            this.setModel(result);
        }
    }

    private AttributeContext getAttributeContext() {
        return metaCrudSession.getAttributeContext();
    }

    public Class getModelClass() {
        return getMetaEntry().getEntityClass();
    }

    public String getModelClassName() {
        return getMetaEntry().getEntityClass().getName();
    }

    public String getModelId() {
        return metaActionHandler.getModelId(this.getModel());
    }

    public String getItemClassName(Object item) {
        if (null == item) {
            return "";
        }
        return item.getClass().getName();
    }

    public String getItemId(final Object item) {
        return metaActionHandler.getModelId(item);
    }

    private MetaEntry getMetaEntry() {
        return getMetaEntryState().getMetaEntry();
    }

    private MetaEntryState getMetaEntryState() {
        return metaCrudSession.getMetaEntryState();
    }

    private void addInfoMessage(final String message) {
        addMessage(FacesMessage.SEVERITY_INFO, message);
    }

    private void addErrorMessage(final String message) {
        addMessage(FacesMessage.SEVERITY_ERROR, message);
    }

    private void addMessage(final FacesMessage.Severity severity, final String msg) {
        final FacesMessage facesMsg = new FacesMessage(severity, msg, msg);
        FacesContext.getCurrentInstance().addMessage(null, facesMsg);
    }

    private Locale getCurrentLocale() {
        final Locale currentLocale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
        return currentLocale;
    }

    public boolean getHasValidationErrors() {
        return FacesUtils.getHasValidationErrors();
    }

    public String getLabelEntity() {
        final Class entityClass = getMetaEntry().getEntityClass();
        return labelResolver.labelClass(entityClass, getCurrentLocale());
    }

    public List<MetaAction> getOtherActions() {
        return getMetaEntry().getOtherActions();
    }

    public List<MetaAction> getOtherContextActions() {
        return getMetaEntry().getOtherContextActions();
    }

    public List<MetaAction> getFinderActions() {
        return getMetaEntry().getFinderActions();
    }

    public List<MetaAction> getFactoryActions() {
        return getMetaEntry().getFactoryActions();
    }

    public List<MetaAction> getContextActions() {
        return getMetaEntry().getContextActions();
    }

    public List<MetaField> getListFields() {
        return getMetaEntry().getListFields();
    }

    public List<MetaField> getDetailFields() {
        return getMetaEntry().getDetailFields();
    }

    public List<MetaField> getFormFields() {
        return getMetaEntry().getFormFields();
    }

    public String labelClass(final Class entityClass) {
        return labelResolver.labelClass(entityClass, getCurrentLocale());
    }

    public String labelAction(final MetaAction action) {
        return labelResolver.labelAction(action, getCurrentLocale());
    }

    public String labelField(final MetaField field, final Object owner) {
        return labelResolver.labelField(field, owner, getCurrentLocale());
    }

    public String templateSrc(final String catalogModifier, final MetaField metaField) {
        final String src = this.catalog.templateSrc(catalogModifier, metaField);
        return src;
    }

    //TODO: is this used ?.. should be replaced  by disableXXX infrastructure ?
    public <T> boolean isNew(final T entity) {
        return metaActionHandler.isNew(entity);
    }

    public String modelTitle(Object model) {
        return metaActionHandler.modelTitle(model);
    }

    public boolean isDisabled(final MetaAction metaAction, final Object model) {
        return metaActionHandler.isDisabled(metaAction, model);
    }

    public boolean isHidden(final MetaAction metaAction, final Object model) {
        return metaActionHandler.isHidden(metaAction, model);
    }

    public SelectOption[] getSelectOptions(final MetaField metaField) {
        final List<SelectOption> list = optionBuilder.getOptions(metaField);
        final SelectOption[] result = new SelectOption[list.size()];
        list.toArray(result);
        return result;
    }

    public Converter getGenericEntityConverter() {
        return this.genericEntityConverter;
    }

    public String getSourceIndex() {
        return sourceIndex;
    }

    public void setSourceIndex(String sourceIndex) {
        this.sourceIndex = sourceIndex;
    }

    public String getTargetIndex() {
        return targetIndex;
    }

    public void setTargetIndex(String targetIndex) {
        this.targetIndex = targetIndex;
    }

    public boolean isRemovableCollection(final MetaField metaField) {
        return entityResolver.isRemovableCollection(metaField);
    }

    public boolean isOrderableCollection(final MetaField metaField) {
        return entityResolver.isOrderableCollection(metaField);
    }

    public void moveTo(final List list) {
        try {
            final int source = Integer.valueOf(getSourceIndex());
            final int target = Integer.valueOf(getTargetIndex());
            collectionHandler.moveTo(list, source, target);
        } catch (NumberFormatException e) {
        }
    }

    public void moveUp(final List list, final int currentIndex) {
        collectionHandler.moveUp(list, currentIndex);
    }

    public void moveDown(final List list, final int currentIndex) {
        collectionHandler.moveDown(list, currentIndex);
    }

    public void removeElement(Collection collection, Object element) {
        collectionHandler.removeElement(collection, element);
    }

    public List convertToList(Collection collection) {
        return collectionHandler.convertToList(collection);
    }

////////////
    public PageHelper<Object> all(final String pageNum) {

        final boolean isPageNumEmpty = null == pageNum || "".equals(pageNum);
        final String currentPageNum = (true == isPageNumEmpty) ? "1" : pageNum;
        if (null == all) {
            final Finder allFinder = allFinder();
            all = fromFinder(allFinder).load(currentPageNum);
        }
        return all;
    }

    private static PageHelper fromFinder(final Finder finder) {
        final PageHelper ph = PageHelper.newInstance(new PageHelper.Finder<Object>() {

            @Override
            public long counter(Object... params) {
                return finder.counter(params);
            }

            @Override
            public List<Object> finder(int firstResult, int maxResult, Object... params) {
                return finder.finder(firstResult, maxResult, params);
            }
        });
        return ph;
    }

    private void resetListHelper() {
        this.all = null;
        //this.lazyDataModel = null;
        this.getMetaEntryState().setLazyDataModel(null);
    }
/////// introducing primefaces 
    //private LazyDataModel<Object> lazyDataModel;

    public LazyDataModel<Object> getLazyDataModel() {

        final LazyDataModel<Object> lazyDataModel = this.getMetaEntryState().getLazyDataModel();
        if (null != lazyDataModel) {
            return lazyDataModel;
        }


        final Finder finder = allFinder();
        final MetaEntry metaEntry = getMetaEntry();
        final LazyDataModel<Object> newLazyDataModel = new LazyTableModel(finder, metaActionHandler, metaEntry);
        newLazyDataModel.setRowCount((int) finder.counter());
        this.getMetaEntryState().setLazyDataModel(newLazyDataModel);

        return newLazyDataModel;
    }
    private Object selectedItem;

    public Object getSelectedItem() {
        return selectedItem;
    }

    public void setSelectedItem(Object selectedItem) {
        this.selectedItem = selectedItem;
    }

    public void onRowSelect(final SelectEvent selectEvent) {
        final Object selectedObject = selectEvent.getObject();
        this.setSelectedItem(selectedObject);
    }
}
